pub mod doudian;
pub mod fnutils;

pub mod spider {
    use std::collections::HashMap;

    use anyhow::Result;

    use chromiumoxide::{
        browser::{Browser, BrowserConfig},
        cdp::browser_protocol::input::{
            DispatchMouseEventParams, DispatchMouseEventType, MouseButton,
        },
        Page,
    };

    use futures::StreamExt;
    use jsonpath_rust::{JsonPathFinder, JsonPathValue};
    use serde_json::{json, Value};
    use tokio::{
        fs::File,
        io::AsyncWriteExt,
        task::JoinHandle,
        time::{sleep, Duration},
    };

    // 处理验证码逻辑
    async fn handle_validate(page: &Page) -> Result<()> {
        let mut point = page
            .find_element("span#nc_1_n1z")
            .await?
            .clickable_point()
            .await?;

        let cmd = DispatchMouseEventParams::builder()
            .x(point.x)
            .y(point.y)
            .button(MouseButton::Left);

        page.move_mouse(point)
            .await?
            .execute(
                cmd.clone()
                    .r#type(DispatchMouseEventType::MousePressed)
                    .build()
                    .unwrap(),
            )
            .await?;

        point.x += 100.0;
        page.move_mouse(point).await?;

        sleep(Duration::from_millis(100)).await;
        point.x += 100.0;
        page.move_mouse(point).await?;

        sleep(Duration::from_millis(100)).await;
        point.x += 80.0;
        point.y += 100.0;
        page.move_mouse(point).await?;

        sleep(Duration::from_millis(2000)).await;
        page.execute(
            cmd.r#type(DispatchMouseEventType::MouseReleased)
                .build()
                .unwrap(),
        )
        .await?;

        Ok(())
    }

    // 启动浏览器
    pub async fn launch_browser() -> Result<(Browser, JoinHandle<()>)> {
        let (browser, mut handler) = Browser::launch(
            BrowserConfig::builder()
                .no_sandbox()
                .with_head()
                //.arg("--disable-dev-shm-usage")
                .arg("--disable-blink-features=AutomationControlled")
                .build()
                .unwrap(),
        )
        .await?;

        let handle = tokio::task::spawn(async move {
            while let Some(h) = handler.next().await {
                if h.is_err() {
                    break;
                }
            }
        });

        Ok((browser, handle))
    }

    // 关闭浏览器
    pub async fn close_browser(mut browser: Browser, handle: JoinHandle<()>) -> Result<()> {
        browser.close().await?;
        handle.await?;
        Ok(())
    }

    pub async fn save_string_to_file(data: &str, input_path: &str) -> Result<()> {
        let mut file = File::create(input_path).await?;
        file.write_all(data.as_bytes()).await?;
        Ok(())
    }

    pub fn json_extract(json: &str, json_path: &str) -> Value {
        let finder = JsonPathFinder::from_str(json, json_path).unwrap();
        let slice_of_data: Vec<JsonPathValue<Value>> = finder.find_slice();
        let result: Vec<Value> = slice_of_data
            .iter()
            .map(|item| item.clone().to_data())
            .collect();
        result.first().unwrap().clone()
    }

    pub fn json_extract_array(json: &str, json_path: &str) -> Vec<Value> {
        let finder = JsonPathFinder::from_str(json, json_path).unwrap();
        let slice_of_data: Vec<JsonPathValue<Value>> = finder.find_slice();
        let result: Vec<Value> = slice_of_data
            .iter()
            .map(|item| item.clone().to_data())
            .collect();
        result
    }

    pub async fn init_page(page: &Page) -> Result<()> {
        return Ok(());
    }

    // 爬取1688
    pub async fn spider1688(url: &str) -> Result<Value> {
        let (browser, handle) = launch_browser().await?;

        let page = browser.new_page(url).await?;

        init_page(&page).await?;

        sleep(Duration::from_millis(1000)).await;

        let find_result = page.find_element("span#nc_1_n1z").await;
        if find_result.is_ok() {
            handle_validate(&page).await?;
        }

        let json: String = page
            .evaluate_expression("JSON.stringify(window.__INIT_DATA)")
            .await?
            .into_value()?;

        let name = json_extract(&json, "$..data.title");

        let base_price = json_extract_array(&json, "$..priceModel.currentPrices.*.price");
        let base_price_str: Vec<String> =
            serde_json::from_str(&serde_json::to_string(&base_price)?)?;

        let unit = json_extract(&json, "$..1081181309101.data.unit");
        let main_image_list = json_extract(&json, "$..1081181309881.data.offerImgList");
        let video = json_extract(&json, "$..1081181309881.data.video");

        let mut spec_map = HashMap::new();
        let temp_map = json_extract(&json, "$.data.1081181309201.data");
        for item in temp_map.as_array().unwrap().iter() {
            spec_map.insert(item["name"].as_str().unwrap(), item["values"].to_string());
        }
        let spec_json = serde_json::to_string(&spec_map).unwrap();

        let attr_list: Vec<Value> = json_extract(&json, "$.globalData.skuModel.skuProps")
            .as_array()
            .unwrap()
            .iter()
            .map(|item| json!({"name": item.get("prop"), "optionList": item.get("value")}))
            .collect();

        let price_type = json_extract(
            &json,
            "$.globalData.orderParamModel.orderParam.skuParam.skuPriceType",
        );
        let mut sku_map2 = HashMap::new();

        let mut sku_map = json_extract(&json, "$.globalData.skuModel.skuInfoMap");
        let ss = sku_map.as_object_mut().unwrap();
        for key in ss.keys() {
            let mut val = ss.get(key).unwrap().clone();
            // 如果价格类型是rangePrice，则设置每个值的price值
            if price_type.as_str().unwrap() == "rangePrice" {
                *val.get_mut("price").unwrap() =
                    serde_json::from_str(base_price_str.join(" ~ ").as_str()).unwrap();
            }
            sku_map2.insert(key, val);
        }

        let mut image_list = Vec::new();
        for item in page.find_elements("div.content-detail > p > img").await? {
            let attr = item.attribute("data-lazyload-src").await?;
            if attr.is_some() {
                image_list.push(attr.unwrap());
            }
        }

        let product_data = json!({
            "name": name,
            "basePrice": base_price_str.join(" ~ "),
            "unit": unit,
            "mainImageList": main_image_list,
            "detailImageList": image_list,
            "specDetail": spec_json,
            "video": video,
            "attrList": attr_list,
            "skuMap": sku_map2
        });

        close_browser(browser, handle).await?;
        Ok(product_data)
    }

    #[cfg(test)]
    mod test {
        use crate::spider::{close_browser, json_extract, launch_browser, spider1688};
        use anyhow::Result;

        #[tokio::test]
        async fn test1688() -> Result<()> {
            let result = spider1688("https://detail.1688.com/offer/619309324578.html").await?;
            //println!("json: {}", result.0);
            println!("vec: {}", result.to_string());
            Ok(())
        }

        #[tokio::test]
        async fn test_driver() -> Result<()> {
            let (browser, handle) = launch_browser().await?;

            // create a new browser page and navigate to the url
            let page = browser.new_page("https://en.wikipedia.org").await?;

            // find the search bar type into the search field and hit `Enter`,
            // this triggers a new navigation to the search result page
            page.find_element("div#p-search")
                .await?
                .click()
                .await?
                .type_str("Rust programming language")
                .await?
                .press_key("Enter")
                .await?;

            let html = page.wait_for_navigation().await?.content().await?;
            println!("{}", html);

            close_browser(browser, handle).await?;
            Ok(())
        }

        #[test]
        fn extract_data() {
            let json = r#"{"first":{"second":[{"active":2},{"passive":1}]}}"#;
            let json_path = "$.first.second[?(@.active)]";

            let data = json_extract(json, json_path);
            assert_eq!(2, data[0]["active"].as_i64().unwrap());
        }

        #[test]
        fn test_extract2() {
            let json3 = r#"
        {
            "name": "lili",
            "age": 18,
            "teachers": [
                {"name": "A", "age": 30},
                {"name": "B", "age": 31},
                {"name": "C", "age": 32},
                {"name": "D", "age": 33}
            ]
        }"#;

            let result = json_extract(&json3, "$.teachers")[0]
                .as_array()
                .unwrap()
                .clone();

            let data: Vec<String> = result
                .iter()
                .map(|item| {
                    let result = item
                        .as_object()
                        .unwrap()
                        .get("name")
                        .unwrap()
                        .as_str()
                        .unwrap()
                        .to_string();
                    result
                })
                .collect();
            println!("{:?}", data);
        }
    }
}
